package com.common.operationLog.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.Assert;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.common.operationLog.entity.OperationLogEntity;
import com.common.operationLog.mapper.OperationLogMapper;
import com.common.operationLog.pojo.bo.OperationLogBO;
import com.common.operationLog.pojo.bo.OperationLogContext;
import com.common.operationLog.pojo.req.OperationLogReqDTO;
import com.common.operationLog.pojo.res.OperationLogModuleResDTO;
import com.common.operationLog.pojo.res.OperationLogResDTO;
import com.common.operationLog.pojo.res.OperationLogTypeResDTO;
import com.common.operationLog.service.IOperationLogService;
import com.common.operationLog.service.OperationLogModuleType;
import com.common.operationLog.util.JsonFlatUtil;
import com.common.operationLog.util.OperationLogUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;

import com.xiaopeng.common.json.JsonUtil;
import com.xiaopeng.common.page.PageUtil;
import com.xiaopeng.common.page.PageVO;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Supplier;

/**
 * <p>
 * 操作日志记录表 服务实现类
 * </p>
 *
 * @author xiaopeng
 * @since 2025-05-25
 */
@Slf4j
@Service
public class OperationLogServiceImpl extends ServiceImpl<OperationLogMapper, OperationLogEntity> implements IOperationLogService {

    @Resource
    private OperationLogContext operationLogContext;


    @Override
    public List<OperationLogModuleResDTO> getModuleAndTypeList() {
        List<OperationLogModuleType> operationLogModuleTypeList = operationLogContext.getOperationLogModuleTypeList();
        if (CollectionUtils.isEmpty(operationLogModuleTypeList)) {
            return Collections.emptyList();
        }
        Map<String, OperationLogModuleResDTO> moduleGroup = new LinkedHashMap<>();
        for (OperationLogModuleType item : operationLogModuleTypeList) {
            OperationLogModuleResDTO moduleDTO = moduleGroup.get(item.getModuleCode());
            if (Objects.isNull(moduleDTO)) {
                moduleDTO = new OperationLogModuleResDTO();
                moduleDTO.setCode(item.getModuleCode());
                moduleDTO.setName(item.getModuleName());
                moduleDTO.setTypeList(new ArrayList<>());
                moduleGroup.put(item.getModuleCode(), moduleDTO);
            }
            OperationLogTypeResDTO typeDTO = new OperationLogTypeResDTO();
            typeDTO.setCode(item.getTypeCode());
            typeDTO.setName(item.getTypeName());
            moduleDTO.getTypeList().add(typeDTO);
        }
        return new ArrayList<>(moduleGroup.values());
    }

    @Override
    public PageVO<OperationLogResDTO> selectPage(OperationLogReqDTO reqDTO) {
        return PageUtil.convertPages(reqDTO, this::selectList);
    }

    @Override
    public List<OperationLogResDTO> selectList(OperationLogReqDTO reqDTO) {
        List<OperationLogEntity> list =  this.lambdaQuery()
                .eq(Objects.nonNull(reqDTO.getBizId()), OperationLogEntity::getBizId, reqDTO.getBizId())
                .eq(StringUtils.isNoneEmpty(reqDTO.getBizCode()), OperationLogEntity::getBizCode, reqDTO.getBizCode())
                .eq(StringUtils.isNoneEmpty(reqDTO.getBizCode()), OperationLogEntity::getBizCode, reqDTO.getBizCode())
                .and(CollectionUtils.isNotEmpty(reqDTO.getOperationModuleCodeList()) || CollectionUtils.isNotEmpty(reqDTO.getOperationTypeCodeList()),
                    e -> {
                        e.in(CollectionUtils.isNotEmpty(reqDTO.getOperationModuleCodeList()), OperationLogEntity::getOperationModuleCode, reqDTO.getOperationModuleCodeList())
                        .or().in(CollectionUtils.isNotEmpty(reqDTO.getOperationTypeCodeList()), OperationLogEntity::getOperationTypeCode, reqDTO.getOperationTypeCodeList());
                    })
                .eq(Objects.nonNull(reqDTO.getOperationBy()), OperationLogEntity::getOperationBy, reqDTO.getOperationBy())
                .eq(StringUtils.isNoneEmpty(reqDTO.getOperationByName()), OperationLogEntity::getOperationByName, reqDTO.getOperationByName())
                .ge(Objects.nonNull(reqDTO.getOperationTimeStart()), OperationLogEntity::getOperationTime, reqDTO.getOperationTimeStart())
                .le(Objects.nonNull(reqDTO.getOperationTimeEnd()), OperationLogEntity::getOperationTime, reqDTO.getOperationTimeEnd())
                .orderByDesc(OperationLogEntity::getOperationTime)
                .list();

        return list.stream().map(item -> {
            OperationLogResDTO resultDTO = BeanUtil.copyProperties(item, OperationLogResDTO.class);
            resultDTO.setChangeContentList(JsonUtil.parseList(item.getChangeContent(), String.class));
            return resultDTO;
        }).toList();
    }

    @Override
    public void save(OperationLogBO operationLogBO) {
        try {
            OperationLogEntity operationLogEntity = new OperationLogEntity();
            operationLogEntity.setId(IdWorker.getId());
            if (Objects.isNull(operationLogBO.getOperationBy()) && StringUtils.isEmpty(operationLogBO.getOperationByName())) {
                // 若未设置操作人信息 默认从上下文中获取操作人信息
                Optional.ofNullable(operationLogContext.getOperationLogUserInfoContextSupplier())
                        .map(Supplier::get)
                        .ifPresent(userInfoContext -> {
                            operationLogEntity.setOperationBy(userInfoContext.getOperationBy());
                            operationLogEntity.setOperationByName(userInfoContext.getOperationByName());
                        });
            } else {
                operationLogEntity.setOperationBy(operationLogBO.getOperationBy());
                operationLogEntity.setOperationByName(operationLogBO.getOperationByName());
            }
            LocalDateTime operationTime = Optional.ofNullable(operationLogBO.getOperationTime()).orElse(LocalDateTime.now());
            operationLogEntity.setOperationTime(operationTime);
            operationLogEntity.setUpdateTime(operationTime);
            OperationLogModuleType operationLogModuleType = operationLogBO.getOperationLogModuleType();
            Assert.notNull(operationLogModuleType, "操作日志模块和类型不能为空");
            operationLogEntity.setOperationModuleCode(operationLogModuleType.getModuleCode());
            operationLogEntity.setOperationModuleName(operationLogModuleType.getModuleName());
            operationLogEntity.setOperationTypeCode(operationLogModuleType.getTypeCode());
            operationLogEntity.setOperationTypeName(operationLogModuleType.getTypeName());
            operationLogEntity.setBizId(operationLogBO.getBizId());
            operationLogEntity.setBizCode(operationLogBO.getBizCode());
            operationLogEntity.setRemark(operationLogBO.getRemark());
            // 操作内容
            ObjectMapper objectMapper = new ObjectMapper();
            ObjectNode beforeNode = OperationLogUtil.convertObjectNode(operationLogBO.getBeforeContent());
            ObjectNode afterNode = OperationLogUtil.convertObjectNode(operationLogBO.getAfterContent());
            operationLogEntity.setRequestParams(objectMapper.writeValueAsString(operationLogBO.getRequestParams()));
            operationLogEntity.setBeforeContent(objectMapper.writeValueAsString(beforeNode));
            operationLogEntity.setAfterContent(objectMapper.writeValueAsString(afterNode));
            operationLogEntity.setChangeContent(objectMapper.writeValueAsString(this.buildChangeContent(beforeNode, afterNode)));
            this.save(operationLogEntity);
        } catch (Exception e) {
            log.error("保存操作日志异常 bo:{}", JSONUtil.toJsonStr(operationLogBO), e);
        }
    }

    /**
     * 构建变更内容
     * @param beforeNode
     * @param afterNode
     * @return
     */
    private List<String> buildChangeContent(ObjectNode beforeNode, ObjectNode afterNode) {
        List<String> changeContentList = new ArrayList<>();
        Map<String, Object> beforeMap = JsonFlatUtil.flat(beforeNode);
        Map<String, Object> afterMap = JsonFlatUtil.flat(afterNode);
        Set<String> ketSet = new LinkedHashSet<>();
        ketSet.addAll(beforeMap.keySet());
        ketSet.addAll(afterMap.keySet());
        for (String key : ketSet) {
            Object beforeValue = beforeMap.get(key);
            Object afterValue = afterMap.get(key);
            if (!Objects.equals(beforeValue, afterValue)) {
                changeContentList.add(StrUtil.format("{}: {} => {}",
                        key, String.valueOf(beforeValue), String.valueOf(afterValue)));
            }
        }
        return changeContentList;
    }

}
